/**
 * 
 */
package com.wy2.generator.database;

import java.io.Closeable;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import cn.xo68.core.date.DateTime;
import cn.xo68.core.util.StringTools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.wy2.generator.config.DatabaseConsts;
import com.wy2.generator.database.common.DatabaseMetaDataEntity;
import com.wy2.generator.database.entity.DatabaseColumn;
import com.wy2.generator.database.entity.DatabaseTable;
import com.wy2.generator.resource.Messages;

/**
 * 元数据工厂
 * @author nnsword
 * @date 2017-12-11 23:56:18
 *
 */
public class MetaDataFactory implements Closeable {
	
	private static final Logger logger=LoggerFactory.getLogger(MetaDataFactory.class);
	
	/**
	 * 数据库连接
	 */
	private final Connection connection;
	
	private final String catalog;
	private final String schema;
	

	/**
	 * @param connection
	 */
	public MetaDataFactory(Connection connection,String catalog,String schema) {
		super();
		this.connection = connection;
		this.catalog=catalog;
		this.schema=schema;
	}

	/* (non-Javadoc)
	 * @see java.io.Closeable#close()
	 */
	public void close() {		
		try {
			if(this.connection!=null && !this.connection.isClosed()) {
				this.connection.close();
			}
			
		} catch (SQLException e) {
			logger.error(Messages.getString("RuntimeError.0"), e);
		}
		
	}
	
	private DatabaseMetaData getDatabaseMetaData() throws SQLException {
		return this.connection.getMetaData();
	}
	
	public String getDatabaseProductName() throws SQLException {
		return getDatabaseMetaData().getDatabaseProductName().toLowerCase();
	}
	public String getCatalog() throws SQLException {
	    return this.catalog;
//		if(StringTools.isEmpty(this.connection.getCatalog())) {
//			return this.catalog;
//		}
//		return this.connection.getCatalog();
	}
	public String getSchema() throws SQLException {
	    return this.schema;
//		if(StringTools.isEmpty(this.connection.getSchema())) {
//			return this.schema;
//		}
//		return this.connection.getSchema();
	}
	
	public List<DatabaseTable> listTables(String tableSqlName) throws SQLException{
	    
	    String localCatalog=getCatalog();
	    String localSchema=getSchema();
	    if(!getDatabaseMetaData().storesLowerCaseIdentifiers() && getDatabaseMetaData().storesUpperCaseIdentifiers()) {
	        if(!StringTools.isEmpty(localCatalog)) {
	            localCatalog=localCatalog.toUpperCase();
	        }
	        
	        if(!StringTools.isEmpty(localSchema)) {
	            localSchema=localSchema.toUpperCase();
	        }
	        if(!StringTools.isEmpty(tableSqlName)){
	            tableSqlName=tableSqlName.toUpperCase();
	        }
	    }else {
	        if(!StringTools.isEmpty(localCatalog)) {
	            localCatalog=localCatalog.toLowerCase();
	        }
	        
	        if(!StringTools.isEmpty(localSchema)) {
	            localSchema=localSchema.toLowerCase();
	        }
	        if(!StringTools.isEmpty(tableSqlName)){
	            tableSqlName=tableSqlName.toLowerCase();	            
	        }
	    }
        
		ResultSet rs = getDatabaseMetaData().getTables(localCatalog,localSchema, tableSqlName, new String[] {DatabaseConsts.DB_META_TABLE_TYPE_TABLE });
		
		
		List<DatabaseTable> dbtables=new ArrayList<DatabaseTable>();
		DatabaseTable databaseTable;
		String catalog;
		String schemaName;
		String realTableName;
		String tableType;
		String remarks;
		while(rs.next()) {
			catalog= rs.getString(DatabaseConsts.DB_META_TABLE_CAT);
			schemaName = rs.getString(DatabaseConsts.DB_META_TABLE_SCHEM) == null ? null : rs.getString(DatabaseConsts.DB_META_TABLE_SCHEM);
			realTableName = rs.getString(DatabaseConsts.DB_META_TABLE_NAME);
			tableType = rs.getString(DatabaseConsts.DB_META_TABLE_TYPE);
			remarks = rs.getString(DatabaseConsts.DB_META_TABLE_REMARKS);
			databaseTable=new DatabaseTable(catalog,schemaName, "", realTableName, remarks,tableType);
			databaseTable.setChecked(true);
			dbtables.add(databaseTable);
		}			
		rs.close();
		return dbtables;
	}
	
	public List<DatabaseColumn> listColumns(DatabaseTable table) throws SQLException {
	    String localCatalog=getCatalog();
        String localSchema=getSchema();
        String tableSqlName=table.getSqlName();
        if(!getDatabaseMetaData().storesLowerCaseIdentifiers() && getDatabaseMetaData().storesUpperCaseIdentifiers()) {
            localCatalog=localCatalog.toUpperCase();
            localSchema=localSchema.toUpperCase();
            tableSqlName=tableSqlName.toUpperCase();
        }else {
            localCatalog=localCatalog.toLowerCase();
            localSchema=localSchema.toLowerCase();
            tableSqlName=tableSqlName.toLowerCase();
        }
	    
		ResultSet rsColumn = getDatabaseMetaData().getColumns(localCatalog,localSchema, tableSqlName, null);
		
		boolean supportsIsAutoIncrement = false;
        boolean supportsIsGeneratedColumn = false;
		ResultSetMetaData rsMetaData = rsColumn.getMetaData();
	    int columnCount=rsMetaData.getColumnCount();
	    for (int i = 1; i <= columnCount; i++) {
            if (DatabaseConsts.DB_META_COLUMN_IS_AUTOINCREMENT.equals(rsMetaData.getColumnName(i))) { //$NON-NLS-1$
                supportsIsAutoIncrement = true;
            }
            if (DatabaseConsts.DB_META_COLUMN_IS_GENERATEDCOLUMN.equals(rsMetaData.getColumnName(i))) { //$NON-NLS-1$
                supportsIsGeneratedColumn = true;
            }
        }
	    
		List<DatabaseColumn> columns=new ArrayList<DatabaseColumn>();
		DatabaseColumn column;
		while(rsColumn.next()) {
			column=new DatabaseColumn(table);
			column.setChecked(true);
			column.setSqlName(rsColumn.getString(DatabaseConsts.DB_META_COLUMN_NAME));
			column.setSqlTypeName(rsColumn.getString(DatabaseConsts.DB_META_COLUMN_TYPE_NAME));
			column.setSqlType(rsColumn.getInt(DatabaseConsts.DB_META_COLUMN_DATA_TYPE));
			column.setSize(rsColumn.getInt(DatabaseConsts.DB_META_COLUMN_SIZE));
			column.setRemarks(rsColumn.getString(DatabaseConsts.DB_META_COLUMN_REMARKS));
			column.setNullable(DatabaseMetaData.columnNullable == rsColumn.getInt(DatabaseConsts.DB_META_COLUMN_NULLABLE));
			column.setDecimalDigits(rsColumn.getInt(DatabaseConsts.DB_META_COLUMN_DECIMAL_DIGITS));
			column.setDefaultValue(rsColumn.getString(DatabaseConsts.DB_META_COLUMN_DEFAULT_VALUE));
			if (supportsIsAutoIncrement) {
				column.setAutoIncrement(DatabaseConsts.DB_META_COLUMN_YES.equals(rsColumn.getString(DatabaseConsts.DB_META_COLUMN_IS_AUTOINCREMENT))); //$NON-NLS-1$ //$NON-NLS-2$
            }
            
            if (supportsIsGeneratedColumn) {
            	column.setGeneratedColumn(DatabaseConsts.DB_META_COLUMN_YES.equals(rsColumn.getString(DatabaseConsts.DB_META_COLUMN_IS_GENERATEDCOLUMN))); //$NON-NLS-1$ //$NON-NLS-2$
            }
            
			columns.add(column);
		}
		rsColumn.close();
		return columns;
	}
	
	public List<String> listPrimaryKeys(DatabaseTable table) throws SQLException{
	    String localCatalog=getCatalog();
        String localSchema=getSchema();
        String tableSqlName=table.getSqlName();
        if(!getDatabaseMetaData().storesLowerCaseIdentifiers() && getDatabaseMetaData().storesUpperCaseIdentifiers()) {
            localCatalog=localCatalog.toUpperCase();
            localSchema=localSchema.toUpperCase();
            tableSqlName=tableSqlName.toUpperCase();
        }else {
            localCatalog=localCatalog.toLowerCase();
            localSchema=localSchema.toLowerCase();
            tableSqlName=tableSqlName.toLowerCase();
        }
		ResultSet rs = getDatabaseMetaData().getPrimaryKeys(localCatalog,localSchema, tableSqlName);
		
		List<String> primaryKeys=new ArrayList<String>();
		// keep primary columns in key sequence order
        Map<Short, String> keyColumns = new TreeMap<Short, String>();
        while (rs.next()) {
            String columnName = rs.getString(DatabaseConsts.DB_META_COLUMN_NAME); //$NON-NLS-1$
            short keySeq = rs.getShort(DatabaseConsts.DB_META_COLUMN_KEY_SEQ); //$NON-NLS-1$
            keyColumns.put(keySeq, columnName);
        }
        for (String columnName : keyColumns.values()) {
        	primaryKeys.add(columnName);
        }
        rs.close();
        return primaryKeys;
	}
	
	public void builderColumns(DatabaseTable table) throws SQLException {
		List<DatabaseColumn> columns = this.listColumns(table);
		table.addColumns(columns);
		List<String> primaryKeys = this.listPrimaryKeys(table);
		table.addPrimaryKeys(primaryKeys);
		table.markPrimaryKey();
	}
	
	public void resolverJdbcType(DatabaseTable table,JdbcMetaDataResolver jdbcMetaDataResolver,String...tablePrefixs) {
		Map<Integer,String> removePrefixResult=StringTools.removePrefix(table.getSqlName(), true, tablePrefixs);
		if(StringTools.isNotEmpty(removePrefixResult.get(1))) {
			table.setPrefix(removePrefixResult.get(1));
		}else {
		    table.setPrefix(StringTools.EMPTY);
		}
		table.setEntityName(jdbcMetaDataResolver.getEntityNameByTableSqlName(removePrefixResult.get(0)));
		for (DatabaseColumn column : table.getColumns()) {			
			column.setFullJavaType(jdbcMetaDataResolver.getFullJavaTypeBySqlType(column));
			column.setJavaProperty(jdbcMetaDataResolver.getJavaPropertyBySqlName(column.getSqlName()));
		}
	}
	/**
	 * 构造数据库表信息
	 * @param tableSqlName 数据库表名称 null表示所有表
	 * @param jdbcMetaDataResolver 
	 * @param tablePrefixs
	 * @return
	 * @throws SQLException
	 */
	public List<DatabaseTable> builderTables(String tableSqlName,JdbcMetaDataResolver jdbcMetaDataResolver,String...tablePrefixs) throws SQLException{
		List<DatabaseTable> dbtables=listTables(tableSqlName);
		for (DatabaseTable table : dbtables) {
			builderColumns(table);
		}		
		for (DatabaseTable table : dbtables) {			
			resolverJdbcType(table,jdbcMetaDataResolver,tablePrefixs);
		}
		return dbtables;
	}
	public DatabaseMetaDataEntity builderDatabaseMetaDataEntity(String tableSqlName,JdbcMetaDataResolver jdbcMetaDataResolver,String...tablePrefixs) throws SQLException{
        List<DatabaseTable> dbtables=builderTables(tableSqlName,jdbcMetaDataResolver,tablePrefixs);
        
        DatabaseMetaDataEntity databaseMetaDataEntity=new DatabaseMetaDataEntity();
        databaseMetaDataEntity.setUpdateTime(DateTime.Now().getDate());
        databaseMetaDataEntity.setTables(dbtables);
        return databaseMetaDataEntity;
    }
	
	

}
